﻿using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Reflection;
using BoyInABox;

public class HS_BoneFixer : MonoBehaviour
{
    #region Variables

    [Header("---------- Adjust Bones Properties ----------")]
    [Tooltip("把整个父物体拽过来")]
    public Transform rootBone;

    public Animator animator;

    [Tooltip("模型大小设置")]
    public float scaleFactor = 12f;

    [Tooltip("人形骨信息")]
    public HS_HumanoidBone humanoidBone;

    [Tooltip("旋转骨")]
    public List<CombineBone> combineBones = new List<CombineBone>();

    [Tooltip("变形骨")]
    public List<DeformBone> deformBones = new List<DeformBone>();

    // ---------- Data Variables ----------
    [Tooltip("更新旋转骨")]
    public bool m_updateCombineBones;

    [Tooltip("更新变形骨")]
    public bool m_updateDeformBones;

    private CombineBoneDatas m_combineBoneDatas = new CombineBoneDatas();
    private DeformBoneDatas m_deformBoneDatas = new DeformBoneDatas();

    private Dictionary<string, Transform> m_bonesDic = new Dictionary<string, Transform>();

    #endregion Variables

    #region BuiltIn Methods

    private void Start()
    {
        Init();
    }

    private void LateUpdate()
    {
        UpdateCombineBone();
        UpdateDeformBone();
    }

    #endregion BuiltIn Methods

    #region Init

    private void Init()
    {
        LoadBonesDic();
    }

    #endregion Init

    #region Handle Bones

    private void UpdateCombineBone()
    {
        if (!m_updateCombineBones || combineBones.Count <= 0)
            return;

        foreach (var bone in combineBones)
        {
            foreach (var followBone in bone.followBones)
            {
                followBone.followBone.localRotation = Quaternion.Lerp(Quaternion.identity, bone.mainBone.localRotation, followBone.weight);
                //followBone.followBone.eulerAngles = Vector3.Slerp(Vector3.zero, bone.mainBone.eulerAngles, followBone.weight);
            }
        }
    }

    private void UpdateDeformBone()
    {
        if (!m_updateDeformBones || deformBones.Count <= 0)
            return;

        foreach (var deformBone in deformBones)
        {
            foreach (var followBone in deformBone.followBones)
            {
                float curAngle = TargetMainAngle(deformBone.mainBone, followBone.mainAngleDir);

                if (curAngle > followBone.minDeformAngle && curAngle < followBone.maxDeformAngle)
                {
                    float percent = (curAngle - followBone.minDeformAngle) / (followBone.maxDeformAngle - followBone.minDeformAngle);
                    float factor = followBone.maxDeformScale - followBone.minDeformScale;

                    followBone.followBone.localScale = TargetFollowScale(followBone.followBone, factor * percent + followBone.minDeformScale, followBone.followAngleDir);
                }
                else if (curAngle <= followBone.minDeformAngle)
                {
                    followBone.followBone.localScale = TargetFollowScale(followBone.followBone, followBone.minDeformScale, followBone.followAngleDir);
                }
                else if (curAngle >= followBone.maxDeformAngle)
                {
                    followBone.followBone.localScale = TargetFollowScale(followBone.followBone, followBone.maxDeformScale, followBone.followAngleDir);
                }
            }
        }
    }

    #endregion Handle Bones

    #region Handle Bones Tool

    private float TargetMainAngle(Transform target, HS_TransformVector dir)
    {
        switch (dir)
        {
            case HS_TransformVector.X:
                return GetInspectorRotationValue(target).x;

            case HS_TransformVector.Y:
                return GetInspectorRotationValue(target).y;

            case HS_TransformVector.Z:
                return GetInspectorRotationValue(target).z;

            default:
                return GetInspectorRotationValue(target).x;
        }
    }

    private Vector3 TargetFollowScale(Transform target, float scale, HS_TransformVector dir)
    {
        Vector3 tmpScale = target.localScale;

        switch (dir)
        {
            case HS_TransformVector.X:
                tmpScale.x = scale;
                return tmpScale;

            case HS_TransformVector.Y:
                tmpScale.y = scale;
                return tmpScale;

            case HS_TransformVector.Z:
                tmpScale.z = scale;
                return tmpScale;
        }

        return tmpScale;
    }

    private Vector3 GetInspectorRotationValue(Transform transform)
    {
        System.Type transformType = transform.GetType();
        PropertyInfo m_propertyInfo_rotationOrder = transformType.GetProperty("rotationOrder", BindingFlags.Instance | BindingFlags.NonPublic);
        object m_OldRotationOrder = m_propertyInfo_rotationOrder.GetValue(transform, null);
        MethodInfo m_methodInfo_GetLocalEulerAngles = transformType.GetMethod("GetLocalEulerAngles", BindingFlags.Instance | BindingFlags.NonPublic);
        object value = m_methodInfo_GetLocalEulerAngles.Invoke(transform, new object[] { m_OldRotationOrder });

        string temp = value.ToString();

        temp = temp.Remove(0, 1);
        temp = temp.Remove(temp.Length - 1, 1);

        string[] tempVector3;
        tempVector3 = temp.Split(',');

        Vector3 vector3 = new Vector3(float.Parse(tempVector3[0]), float.Parse(tempVector3[1]), float.Parse(tempVector3[2]));
        return vector3;
    }

    /// <summary>
    /// 加载HS人形骨骼信息
    /// </summary>
    public void LoadHSHumanoidBone()
    {
        try
        {
            humanoidBone.SetHSHumanoidBones(animator);
        }
        catch (System.Exception e)
        {
            Debug.LogWarning("No Valid Bone Loaded! Error : " + e + " Check If Avatar/Animator Set Correctly Or Not !");
        }
    }

    /// <summary>
    /// 加载骨骼信息入字典（获取骨骼的前置）
    /// </summary>
    public void LoadBonesDic()
    {
        m_bonesDic.Clear();

        Transform[] tmpBones = rootBone.GetComponentsInChildren<Transform>();
        foreach (Transform bone in tmpBones)
        {
            if (!m_bonesDic.ContainsKey(bone.name))
                m_bonesDic.Add(bone.name, bone);
        }
    }

    /// <summary>
    /// 获取骨骼（获取前保证骨骼信息已加载）
    /// </summary>
    /// <param name="boneName">骨骼名称</param>
    /// <param name="fuzzy">是否模糊搜索</param>
    /// <returns></returns>
    public Transform GetBone(string boneName, bool fuzzy = false)
    {
        if (!fuzzy)
        {
            try
            {
                return m_bonesDic[boneName];
            }
            catch (System.Exception e)
            {
                Debug.LogWarning("No Valid Bone" + "{" + boneName + "}" + "Detected! Error : " + e);
                return null;
            }
        }
        else
        {
            try
            {
                foreach (var name in m_bonesDic.Keys)
                {
                    if (name.Contains(boneName))
                        return m_bonesDic[name];
                }

                return m_bonesDic[boneName];
            }
            catch (System.Exception e)
            {
                Debug.LogWarning("No Valid Bone Contained" + "{" + boneName + "}" + "Detected! Error : " + e);
                return null;
            }
        }
    }

    #endregion Handle Bones Tool

    #region Reset Bones Layer

    private void ResetExtraEmptyBonesLayer()
    {
        // p_cf_anim
        ExtraEmptyBones extraEmptyBones = new ExtraEmptyBones();

        foreach (var boneName in extraEmptyBones.boneNames)
        {
            try { DestroyImmediate(GetBone(boneName).gameObject); } catch { }
        }
    }

    private void ResetBreastBonesLayer()
    {
        try
        {
            ExtraMuneBones extraMuneBones = new ExtraMuneBones();

            // Left Breast Bones
            Transform l_breast = GetBone("cf_J_Mune00_t_L");
            Transform l_breast00 = GetBone("cf_J_Mune00_s_L");
            Transform l_breast01 = GetBone("cf_J_Mune01_s_L");
            Transform l_breast02 = GetBone("cf_J_Mune02_s_L");
            Transform l_breast03 = GetBone("cf_J_Mune03_s_L");

            l_breast03.SetParent(l_breast02);
            l_breast03.SetAsFirstSibling();
            DestroyImmediate(l_breast02.GetChild(1).gameObject);

            l_breast02.SetParent(l_breast01);
            l_breast02.SetAsFirstSibling();
            DestroyImmediate(l_breast01.GetChild(1).gameObject);

            l_breast01.SetParent(l_breast00);
            l_breast01.SetAsFirstSibling();
            DestroyImmediate(l_breast00.GetChild(1).gameObject);

            DestroyImmediate(l_breast.GetChild(1).gameObject);

            foreach (var boneName in extraMuneBones.leftBoneNames)
            {
                try { DestroyImmediate(GetBone(boneName).gameObject); } catch { }
            }

            // Right Breast Bones
            Transform r_breast = GetBone("cf_J_Mune00_t_R");
            Transform r_breast00 = GetBone("cf_J_Mune00_s_R");
            Transform r_breast01 = GetBone("cf_J_Mune01_s_R");
            Transform r_breast02 = GetBone("cf_J_Mune02_s_R");
            Transform r_breast03 = GetBone("cf_J_Mune02_s_R");

            r_breast03.SetParent(r_breast02);
            r_breast03.SetAsFirstSibling();
            DestroyImmediate(r_breast02.GetChild(1).gameObject);

            r_breast02.SetParent(r_breast01);
            r_breast02.SetAsFirstSibling();
            DestroyImmediate(r_breast01.GetChild(1).gameObject);

            r_breast01.SetParent(r_breast00);
            r_breast01.SetAsFirstSibling();
            DestroyImmediate(r_breast00.GetChild(1).gameObject);

            DestroyImmediate(r_breast.GetChild(1).gameObject);

            foreach (var boneName in extraMuneBones.rightBoneNames)
            {
                try { DestroyImmediate(GetBone(boneName).gameObject); } catch { }
            }

            Debug.Log("Breast Bones Layer Set");
        }
        catch (System.Exception e)
        {
            Debug.LogWarning("No Valid Breast Bones Layer Set! Error: " + e);
        }
    }

    private void ResetVaginaBonesLayer()
    {
        try
        {
            ExtraVaginaBones extraVaginaBones = new ExtraVaginaBones();
            Transform vagina_root = GetBone("cf_J_Vagina_root");

            // Front Vagina Inner
            Transform f_inner = GetBone("cf_J_Vagina_Inner_F");
            f_inner.localPosition = new Vector3(0f, -0.483f, 0.072f) * scaleFactor / 12f;

            // Back Vagina Inner
            Transform b_inner = GetBone("cf_J_Vagina_Inner_B");
            b_inner.localPosition = new Vector3(0f, -0.49f, -0.03f) * scaleFactor / 12f;

            // Left Side Vagina Bones
            GameObject l_start = new GameObject("vagina_start_L");
            GameObject l_stop = new GameObject("vagina_stop_L");

            Transform L_side = GetBone("cf_J_Vagina_F_L");
            Transform fl_side = GetBone("cf_J_Vagina_s_F_L");
            Transform mfl_side = GetBone("cf_J_Vagina_s_M_F_L");
            Transform ml_side = GetBone("cf_J_Vagina_s_M_L");
            Transform mbl_side = GetBone("cf_J_Vagina_s_M_B_L");
            Transform bl_side = GetBone("cf_J_Vagina_s_B_L");

            l_start.transform.SetParent(L_side);
            l_start.transform.localPosition = new Vector3(0.0125f, 0.0077f, 0.0252f) * scaleFactor / 12f;

            fl_side.SetParent(l_start.transform);
            mfl_side.SetParent(fl_side);
            ml_side.SetParent(mfl_side);
            mbl_side.SetParent(ml_side);
            bl_side.SetParent(mbl_side);

            l_stop.transform.SetParent(bl_side);
            l_stop.transform.localPosition = new Vector3(0.0089f, -0.0054f, -0.0126f) * scaleFactor / 12f;

            foreach (var boneName in extraVaginaBones.leftBoneNames)
            {
                try { DestroyImmediate(GetBone(boneName).gameObject); } catch { }
            }

            Transform fl_inner = GetBone("cf_J_Vagina_Inner_F_L");
            Transform ml_inner = GetBone("cf_J_Vagina_Inner_L");
            Transform bl_inner = GetBone("cf_J_Vagina_Inner_B_L");

            fl_inner.SetParent(fl_side);
            ml_inner.SetParent(ml_side);
            bl_inner.SetParent(bl_side);

            fl_inner.localPosition = new Vector3(-0.01f, 0.018f, 0f) * scaleFactor / 12f;
            ml_inner.localPosition = new Vector3(-0.01f, 0.018f, 0f) * scaleFactor / 12f;
            bl_inner.localPosition = new Vector3(-0.01f, 0.018f, 0f) * scaleFactor / 12f;

            // Right Side Vagina Bones
            GameObject r_start = new GameObject("vagina_start_R");
            GameObject r_stop = new GameObject("vagina_stop_R");

            Transform R_side = GetBone("cf_J_Vagina_F_R");
            Transform fr_side = GetBone("cf_J_Vagina_s_F_R");
            Transform mfr_side = GetBone("cf_J_Vagina_s_M_F_R");
            Transform mr_side = GetBone("cf_J_Vagina_s_M_R");
            Transform mbr_side = GetBone("cf_J_Vagina_s_M_B_R");
            Transform br_side = GetBone("cf_J_Vagina_s_B_R");

            r_start.transform.SetParent(R_side);
            r_start.transform.localPosition = new Vector3(-0.0125f, 0.0077f, 0.0252f) * scaleFactor / 12f;

            fr_side.SetParent(r_start.transform);
            mfr_side.SetParent(fr_side);
            mr_side.SetParent(mfr_side);
            mbr_side.SetParent(mr_side);
            br_side.SetParent(mbr_side);

            r_stop.transform.SetParent(br_side);
            r_stop.transform.localPosition = new Vector3(-0.0089f, -0.0054f, -0.0126f) * scaleFactor / 12f;

            foreach (var boneName in extraVaginaBones.rightBoneNames)
            {
                try { DestroyImmediate(GetBone(boneName).gameObject); } catch { }
            }

            Transform fr_inner = GetBone("cf_J_Vagina_Inner_F_R");
            Transform mr_inner = GetBone("cf_J_Vagina_Inner_R");
            Transform br_inner = GetBone("cf_J_Vagina_Inner_B_R");

            fr_inner.SetParent(fr_side);
            mr_inner.SetParent(mr_side);
            br_inner.SetParent(br_side);

            fr_inner.localPosition = new Vector3(0.01f, 0.018f, 0f) * scaleFactor / 12f;
            mr_inner.localPosition = new Vector3(0.01f, 0.018f, 0f) * scaleFactor / 12f;
            br_inner.localPosition = new Vector3(0.01f, 0.018f, 0f) * scaleFactor / 12f;

            Debug.Log("Vagina Bones Layer Set");
        }
        catch (System.Exception e)
        {
            Debug.LogWarning("No Valid Vagina Bones Layer Set! Error: " + e);
        }
    }

    public void ResetBonesLayer()
    {
        LoadBonesDic();

        ResetExtraEmptyBonesLayer();
        ResetBreastBonesLayer();
        ResetVaginaBonesLayer();
    }

    #endregion Reset Bones Layer

    #region Addictive Function

    public void AddFacialExpression()
    {
        gameObject.AddComponent<HS_FacialExpression>().InitFacialExpression();
    }

    public void AddClothesChanger()
    {
        gameObject.AddComponent<HS_ClothChanger>().Init();
    }

    public void AddHighHeelFixed()
    {
        gameObject.AddComponent<HS_HighHeelFixed>();
    }

    #endregion Addictive Function

#if UNITY_EDITOR

    #region Combine Bones Data

    public void SaveCombineBonesData()
    {
        try
        {
            ConvertCombineBonesToData();

            string filePath = EditorUtility.SaveFilePanel("保存旋转骨", Application.streamingAssetsPath, "", "json");
            string jsonString = JsonUtility.ToJson(m_combineBoneDatas, true);
            File.WriteAllText(filePath, jsonString);

            Debug.Log("Config Saved");
        }
        catch (System.Exception e)
        {
            Debug.LogWarning("No Valid Config Saved! Error : " + e);
        }
    }

    public void LoadCombineBonesData()
    {
        try
        {
            LoadBonesDic();

            string filePath = EditorUtility.OpenFilePanel("加载旋转骨", Application.streamingAssetsPath, "json");
            string jsonInfo = File.ReadAllText(filePath);
            m_combineBoneDatas = JsonUtility.FromJson<CombineBoneDatas>(jsonInfo);

            ConvertCombineDataToBones();

            Debug.Log("Config Loaded");
        }
        catch (System.Exception e)
        {
            Debug.LogWarning("No Valid Config Loaded! Error : " + e);
        }
    }

    private void ConvertCombineDataToBones()
    {
        combineBones.Clear();

        foreach (var combineBoneData in m_combineBoneDatas.combineBoneDatas)
        {
            CombineBone tmpCombineBone = new CombineBone();
            tmpCombineBone.boneName = combineBoneData.boneName;
            tmpCombineBone.mainBone = GetBone(combineBoneData.mainBoneName);

            foreach (var combineFollowBoneData in combineBoneData.followBoneDatas)
            {
                CombineFollowBone tmpFollowBone = new CombineFollowBone();
                tmpFollowBone.followBone = GetBone(combineFollowBoneData.followBoneName);
                tmpFollowBone.weight = combineFollowBoneData.weight;

                tmpCombineBone.followBones.Add(tmpFollowBone);
            }

            combineBones.Add(tmpCombineBone);
        }
    }

    private void ConvertCombineBonesToData()
    {
        m_combineBoneDatas.combineBoneDatas.Clear();

        foreach (var combineBone in combineBones)
        {
            CombineBoneData tmpCombineBoneData = new CombineBoneData();
            tmpCombineBoneData.boneName = combineBone.boneName;
            tmpCombineBoneData.mainBoneName = combineBone.mainBone.name;
            foreach (var combineFollowBone in combineBone.followBones)
            {
                CombineFollowBoneData tmpCombineFollowBoneData = new CombineFollowBoneData();
                tmpCombineFollowBoneData.followBoneName = combineFollowBone.followBone.name;
                tmpCombineFollowBoneData.weight = combineFollowBone.weight;

                tmpCombineBoneData.followBoneDatas.Add(tmpCombineFollowBoneData);
            }

            m_combineBoneDatas.combineBoneDatas.Add(tmpCombineBoneData);
        }
    }

    #endregion Combine Bones Data

    #region Deform Bones Data

    public void SaveDeformBonesData()
    {
        try
        {
            ConvertDeformBonesToData();

            string filePath = EditorUtility.SaveFilePanel("保存变形骨", Application.streamingAssetsPath, "", "json");
            string jsonString = JsonUtility.ToJson(m_deformBoneDatas, true);
            File.WriteAllText(filePath, jsonString);

            Debug.Log("Config Saved");
        }
        catch (System.Exception e)
        {
            Debug.LogWarning("No Valid Config Saved! Error : " + e);
        }
    }

    public void LoadDeformBonesData()
    {
        try
        {
            LoadBonesDic();

            string filePath = EditorUtility.OpenFilePanel("加载变形骨", Application.streamingAssetsPath, "json");
            string jsonInfo = File.ReadAllText(filePath);
            m_deformBoneDatas = JsonUtility.FromJson<DeformBoneDatas>(jsonInfo);

            ConvertDeformDataToBones();

            Debug.Log("Config Loaded");
        }
        catch (System.Exception e)
        {
            Debug.LogWarning("No Valid Config Loaded! Error : " + e);
        }
    }

    private void ConvertDeformDataToBones()
    {
        deformBones.Clear();

        foreach (var deformBoneData in m_deformBoneDatas.deformBoneDatas)
        {
            DeformBone tmpDeformBone = new DeformBone();
            tmpDeformBone.boneName = deformBoneData.boneName;
            tmpDeformBone.mainBone = GetBone(deformBoneData.mainBoneName);

            foreach (var deformFollowBoneData in deformBoneData.followBoneDatas)
            {
                DeformFollowBone tmpFollowBone = new DeformFollowBone();
                tmpFollowBone.followBone = GetBone(deformFollowBoneData.followBoneName);
                tmpFollowBone.mainAngleDir = deformFollowBoneData.mainAngleDir;
                tmpFollowBone.minDeformAngle = deformFollowBoneData.minDeformAngle;
                tmpFollowBone.maxDeformAngle = deformFollowBoneData.maxDeformAngle;
                tmpFollowBone.followAngleDir = deformFollowBoneData.followAngleDir;
                tmpFollowBone.minDeformScale = deformFollowBoneData.minDeformScale;
                tmpFollowBone.maxDeformScale = deformFollowBoneData.maxDeformScale;

                tmpDeformBone.followBones.Add(tmpFollowBone);
            }

            deformBones.Add(tmpDeformBone);
        }
    }

    private void ConvertDeformBonesToData()
    {
        m_deformBoneDatas.deformBoneDatas.Clear();

        foreach (var deformBone in deformBones)
        {
            DeformBoneData tmpDeformBoneData = new DeformBoneData();
            tmpDeformBoneData.boneName = deformBone.boneName;
            tmpDeformBoneData.mainBoneName = deformBone.mainBone.name;

            foreach (var deformFollowBone in deformBone.followBones)
            {
                DeformFollowBoneData tmpDeformFollowBoneData = new DeformFollowBoneData();
                tmpDeformFollowBoneData.followBoneName = deformFollowBone.followBone.name;
                tmpDeformFollowBoneData.mainAngleDir = deformFollowBone.mainAngleDir;
                tmpDeformFollowBoneData.minDeformAngle = deformFollowBone.minDeformAngle;
                tmpDeformFollowBoneData.maxDeformAngle = deformFollowBone.maxDeformAngle;
                tmpDeformFollowBoneData.followAngleDir = deformFollowBone.followAngleDir;
                tmpDeformFollowBoneData.minDeformScale = deformFollowBone.minDeformScale;
                tmpDeformFollowBoneData.maxDeformScale = deformFollowBone.maxDeformScale;

                tmpDeformBoneData.followBoneDatas.Add(tmpDeformFollowBoneData);
            }

            m_deformBoneDatas.deformBoneDatas.Add(tmpDeformBoneData);
        }
    }

    #endregion Deform Bones Data

#endif
}

#if UNITY_EDITOR

[CustomEditor(typeof(HS_BoneFixer))]
public class HS_BoneFixer_Editor : Editor
{
    public override void OnInspectorGUI()
    {
        DrawDefaultInspector();

        HS_BoneFixer boneAdjuster = target as HS_BoneFixer;

        #region Bones

        GUILayout.Label("---------- Bones Layer ----------");

        if (GUILayout.Button("Fix Bones"))
        {
            boneAdjuster.ResetBonesLayer();
            boneAdjuster.LoadHSHumanoidBone();
            GUIUtility.ExitGUI();
        }

        GUILayout.Label("---------- Combine Bones ----------");

        GUILayout.BeginHorizontal();

        if (GUILayout.Button("Save Combine Bones Data"))
        {
            boneAdjuster.SaveCombineBonesData();
            GUIUtility.ExitGUI();
        }

        if (GUILayout.Button("Load Combine Bones Data"))
        {
            boneAdjuster.LoadCombineBonesData();
            GUIUtility.ExitGUI();
        }

        GUILayout.EndHorizontal();

        GUILayout.Label("---------- Deform Bones ----------");

        GUILayout.BeginHorizontal();

        if (GUILayout.Button("Save Deform Bones Data"))
        {
            boneAdjuster.SaveDeformBonesData();
            GUIUtility.ExitGUI();
        }

        if (GUILayout.Button("Load Deform Bones Data"))
        {
            boneAdjuster.LoadDeformBonesData();
            GUIUtility.ExitGUI();
        }

        GUILayout.EndHorizontal();

        #endregion Bones

        #region Addictive

        GUILayout.Label("---------- Addictive ----------");

        GUILayout.BeginHorizontal();

        if (GUILayout.Button("Add Facial Expression"))
        {
            boneAdjuster.AddFacialExpression();
            GUIUtility.ExitGUI();
        }

        if (GUILayout.Button("Add Clothes Changer"))
        {
            boneAdjuster.AddClothesChanger();
            GUIUtility.ExitGUI();
        }

        if (GUILayout.Button("Add High Heel Fixed"))
        {
            boneAdjuster.AddHighHeelFixed();
            GUIUtility.ExitGUI();
        }

        GUILayout.EndHorizontal();

        #endregion Addictive
    }
}

#endif